#include <stdio.h>
#include <string.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <cassert>
using namespace std;

// 用于处理重定向时使用
enum Redirect
{
    REDIRECT_NONE,   // 没有重定向
    REDIRECT_INPUT,  // 输入重定向
    REDIRECT_OUTPUT, // 输出重定向
    REDIRECT_APPEND, // 追加重定向
};

// 检查重定向的情况
char *CheckRedirect(char *commend, enum Redirect *redirect_type)
{
    // 一直到检查到命令行字符串到'\0'时
    while (*commend)
    {
        // 当遇到>时
        if ((*commend) == '>')
        {
            // 有可能是追加重定向
            if (*(commend + 1) == '>')
            {
                *redirect_type = REDIRECT_APPEND;
                *commend = '\0';    // 后面使用>前面的命令时更加方便
                return commend + 2; // 返回重定向的文件位置
            }
            // 接下来就是输出重定向了
            *redirect_type = REDIRECT_OUTPUT;
            *commend = '\0';
            return commend + 1;
        }

        if ((*commend) == '<')
        {
            // 接下来就是输入重定向
            *redirect_type = REDIRECT_INPUT;
            *commend = '\0';
            return commend + 1;
        }
        commend++;
    }
    *redirect_type = REDIRECT_NONE;
    return nullptr;
}
extern char **environ;

static int ret_code = 0; // 用来记录上一个进程的退出码

char *commend_list[10] = {NULL};
char environment[20][30] = {0}; // 用来存储用户自己的环境变量
int envir_index = 0;
int main()
{
    char commend[1024]; // 将命令存入这个数组
    while (1)
    {
        cout << "[lnb@VM-16-17centos 当前目录]";
        fgets(commend, 1024, stdin);
        // 去掉\n
        commend[strlen(commend) - 1] = '\0';

        // 优先查看一下重定向，后续拆分命令会影响commend的内容,把" "都变成'\0'
        //并且在函数中处理了重定向符号为\0，保证后续拆分cat<log.txt这种起命令只会得到cat
        enum Redirect redirect_type = REDIRECT_NONE;
        char *redirectfile = CheckRedirect(commend, &redirect_type);

        // 拆分命令
        commend_list[0] = strtok(commend, " ");
        int i = 1;
        while (commend_list[i++] = strtok(NULL, " "))
        {
        }

        i=0;
        while(commend_list[i])
        {
            cout<<commend_list[i++]<<endl;
        }

        if (strcmp(commend_list[0], "ls") == 0)
        {
            commend_list[i++] = "--color=auto";
        }
        if (strcmp(commend_list[0], "ll") == 0)
        {
            commend_list[0] = "ls";
            commend_list[1] = "-l";
            commend_list[2] = "--color=auto";
            commend_list[3] = NULL;
        }
        if (strcmp(commend_list[0], "cd") == 0)
        {
            if (commend_list[1] != NULL)
                chdir(commend_list[1]);
            continue;
        }

        if (strcmp(commend_list[0], "export") == 0)
        {
            // 我们要对这个它添加的环境变量进行一个保存，
            // 因为如果不保存会出现环境变量指针消失的问题
            // commend_list[1]中存储了我们的添加的环境变量
            // 但是当我们下一回合重新读取命令的时候，他就会
            // 被覆盖，因为我们的commend_list数组中的指针都是
            // 从commend中出来的，这样的话就会导致结果有问题，
            // 无法正常显示
            if (commend_list[1] != NULL)
            {
                strcpy(environment[envir_index], commend_list[1]);
                putenv(environment[envir_index++]);
                continue;
            }
        }

        if (strcmp(commend_list[0], "env") == 0)
        {
            // 之所以要进行特殊处理env是因为我们要打印显示的是bash父进程本身的环境变量
            // 而不是我们的子进程
            for (int i = 0; environ[i]; i++)
            {
                cout << environ[i] << endl;
            }
            continue;
        }

        if (strcmp(commend_list[0], "echo") == 0 && (*(commend_list[1])) == '$') // 对于查看环境变量值的处理
        {
            if (commend_list[1][1] == '?') // 第一个命令选项中第二个字符
            {
                cout << ret_code << endl;
                continue;
            }
            const char *str = NULL;
            str = getenv(commend_list[1] + 1); // 此处的commend_list[1]的值为char*，指向第一个命令选项，+1后指向$后面
            printf("%s:%s\n", commend_list[1] + 1, str);
            continue;
        }

        // 创建子进程
        pid_t ret = fork();
        if (ret > 0)
        {
            // 父进程,进行等待
            int status = 0;
            waitpid(ret, &status, 0);
            cout << "退出信号:" << (status & 0x7f) << ",退出码:" << ((status >> 8) & 0xff) << endl;
            cout << "退出信号:" << WTERMSIG(status) << ",退出码:" << WEXITSTATUS(status) << endl;
            ret_code = ((status >> 8) & 0xff);
        }
        else
        {
            // 处理重定向>,>>,<

            if (redirect_type != REDIRECT_NONE)
            {
                // 走到这里说明是输入的命令是有重定向的
                if (redirect_type == REDIRECT_INPUT)
                {
                    cout << "处理输入重定向" << endl;
                    int fd = open(redirectfile, O_RDONLY, 0666);
                    dup2(fd, 0);
                }
                else if (redirect_type == REDIRECT_OUTPUT)
                {
                    cout << "处理输出重定向" << endl;
                    int fd = open(redirectfile, O_WRONLY|O_CREAT|O_TRUNC, 0666);
                    dup2(fd, 1);
                }
                else if (redirect_type == REDIRECT_APPEND)
                {
                    cout << "处理追加重定向" << endl;
                    int fd = open(redirectfile, O_WRONLY|O_APPEND|O_CREAT, 0666);
                    dup2(fd, 1);
                }
                else
                {
                    // 走到这说明有问题
                    assert(0);
                }
            }

            // 子进程,进行程序替换
            execvp(commend_list[0], commend_list);
            exit(-1);
        }
    }
    return 0;
}
